package Bench::Test;

use 5.008004;
use strict;
use warnings;

use Pod::Usage;
use File::Basename;

use GRID::Machine;
use Bench::Test::Entry;
use Bench::Test::Base;
use Bench::Test::Local;
use Bench::Test::Remote;
use Bench::Test::View;

our $VERSION = '0.01';

#============================================================================== 
# Object Constructor.
# - Inputs:  Classname and configuration file
# - Outputs: The blessed reference
#============================================================================== 
sub new {
   my ($class, %params) = @_;

   my $config_file = $params{config_file};
  
   # 
   # Auxiliar variables to can evaluate the configuration file
   #
   my ($EXECUTABLE,     # Executable file
       $TEST_FILES,     # Test files
       $PATH_TEST,      # Path to the test ok files
       $MAX_LEN_FILE,   # File to store max length of all fields
       @OUTPUTFORMAT,   # Output format. One of 'text', 'html', 'latex'
       $NOTAVAILABLE,   # Symbol for not available fields
       %EXPERIMENTS,    # Base test, local and remote executions (see Bench::Test::Base, ::Local, and ::Remote)
       @TESTS,          # Array with test names and parameters
       @SELECTED,       # Name of selected experiments to execute
       @FIELDS,         # Observables fields
       @VISUAL,         # Columns to show
       @UNIQUE,         # Fields with the same result in all experiments
       @TABLE);         # Table with regular expressions

   # 
   # Load the configuration file if exist
   #
   if ($config_file) {
      if (-r $config_file) {
         local $/ = undef;
         my $config = '';

         if (ref($config_file)) {
            $config = <$config_file>;
         }
         else { 
            open CONFIG, "< $config_file" or die "Error trying to open $config_file\n";
            $config = <CONFIG>;
            close CONFIG                  or die "Error trying to close $config_file\n";
         }
         eval $config;
         die "Errors in configuration file: $@\n" if ($@);
      }
      else {
         die "An error ocurred when trying to read the configuration file\n";
      }
   }   
   #
   # Load parameters given by user and
   # overwrite the config file parameters if necessary
   #
   exists $params{notavailable}  and do { $NOTAVAILABLE  = $params{notavailable}    };
   exists $params{tests}         and do { @TESTS         = @{$params{tests}}        };
   exists $params{table}         and do { @TABLE         = @{$params{table}}        };
   exists $params{visual}        and do { @VISUAL        = @{$params{visual}}       };
   exists $params{unique}        and do { @UNIQUE        = @{$params{unique}}       };
   exists $params{experiments}   and do { %EXPERIMENTS   = %{$params{experiments}}  };
   exists $params{selected}      and do { @SELECTED      = @{$params{selected}}     };
   exists $params{outputformat}  and do { @OUTPUTFORMAT  = @{$params{outputformat}} };

   #
   # Change the representation of TABLE and VISUAL
   # and generate @FIELDS array
   #
   my %VISUAL;
   my %TABLE = @TABLE;
   map { $VISUAL{$_} = 1 } @VISUAL;
   @FIELDS = @VISUAL;

   #
   # Check TABLE and VISUAL parameters
   #
   @TABLE  or die "You must specify TABLE parameter\n";
   @TESTS  or die "You must specify TESTS parameter\n";

   %VISUAL or die "You must specify VISUAL parameter\n";
   %EXPERIMENTS or die "You must specify EXPERIMENTS parameter\n";
   @SELECTED or die "You must specify SELECTED parameter\n";
 
   check_visual_parameter   (\%TABLE,       \%VISUAL);
   check_unique_parameter   (\%TABLE,       \@UNIQUE);
   check_selected_parameter (\%EXPERIMENTS, \@SELECTED);
   
   # 
   # Create the object
   #
   return bless {
      # 
      # Obligatory parameters
      #
      TABLE         => \%TABLE,
      VISUAL        => \%VISUAL,
      EXPERIMENTS   => \%EXPERIMENTS,
      SELECTED      => \@SELECTED,
      #
      # Optional parameters
      #
      NOTAVAILABLE  => $NOTAVAILABLE  || '-',
      OUTPUTFORMAT  => \@OUTPUTFORMAT || { 'text' => '' },
      UNIQUE        => \@UNIQUE,
      TESTS         => \@TESTS,
      #
      # Calculated parameters. Not given by user
      #
      FIELDS        => \@FIELDS,      
   }, $class;
}

sub check_visual_parameter {
   my ($table, $visual) = @_;
   for my $field (keys %{$visual}) {
      next if $field eq 'TEST';
      next if $field eq 'NAME';
      die "$field field is defined in VISUAL parameter, but not in TABLE parameter\n"
         unless $table->{$field};
   }
}

sub check_unique_parameter {
   my ($table, $unique) = @_;
   for my $field (@{$unique}) {
      die "$field field is defined in UNIQUE parameter, but not in TABLE parameter\n"
         unless $table->{$field};
   }
}

sub check_selected_parameter {
   my ($experiments, $selected) = @_;
   for my $exp (@{$selected}) {
      die "$exp experiment is defined in SELECTED parameter, but not in EXPERIMENTS parameter\n"
         unless $experiments->{$exp};
   }
}

sub get_tests_from_glob {
   my @files = glob shift;

   my @tests = ();

   for my $f (@files) {
      my $basename = basename($f);
      $basename =~ /(.*)\..*/ or $basename =~ /(.*)/;
      push @tests, $1, $f;
   }
   return @tests;
}

#============================================================================== 
# Get test file names from file
# - Inputs:  File name containing the test files
# - Outputs: Array with test file names
#============================================================================== 
sub get_tests_from_file {
   my $filename = shift;
   
   die "An error ocurred when trying to open file: '$filename'\n"
     unless (-r $filename);

   my @tests = ();

   local $/ = "\n";
   
   open TESTS, "< $filename" or die "Error trying to open $filename\n";
   while (<TESTS>) {
      next if /^#/;
      next if /^\s*$/;
      chomp;
      my ($testname, $params) = split ':';
      push @tests, $testname, $params;
   }
   close TESTS               or die "Error trying to close $filename\n";

   return @tests;
}

#==============================================================================
# Find field
# - Inputs: Output of the execution, regular expression
# - Output: Result of evaluate the regular expression
#==============================================================================
sub find_field {
   my ($self, $output, $filter) = @_;

   return $filter->($output) if (ref($filter) eq 'CODE');
   return $1                 if ($output =~ m/$filter/i);
   return $self->{NOTAVAILABLE};
}
#sub find_field {
#   my ($self, $output, $filter) = @_;
#
#   if ($output =~ m/$filter->{REGEXP}/i) {
#      return $1 if (not defined($filter->{CODE}));
#      return $filter->{CODE}->($output);  # Deberia ir en un eval y comprobar errores
#   }
#   return $self->{NOTAVAILABLE};
#}

#============================================================================== 
# Echo values 
# - Inputs:  Experiment entry
# - Outputs: Prints output if primer parameter is true
#============================================================================== 
sub fill_entry_with_fields {
   my $self = shift;
   my ($entry, $exp_name, $output) = @_;


   for my $field (keys %{$self->{TABLE}}) {
      next if ($field eq 'TEST') or ($field eq 'NAME');
      my $field_value = $self->find_field($output, $self->{TABLE}{$field});
      $entry->set_field_value($field, $exp_name, $field_value);
   }

}

#=============================================================================
# Make test compare
#  - Inputs: None (See config file)
#  - Output: Test results
#============================================================================
sub make_tests {
   my $self = shift;
   
   my $entry = Bench::Test::Entry->new(
                                     fields       => $self->{FIELDS},
                                     exp_names    => $self->{SELECTED},
                                     unique       => $self->{UNIQUE},
                                  ); 

   for my $exp_name (@{$self->{SELECTED}}) {
      if ($self->{EXPERIMENTS}{$exp_name}->isa('Bench::Test::Remote')) {
         $self->{EXPERIMENTS}{$exp_name}->connect;
         $self->{EXPERIMENTS}{$exp_name}->execute_preamble;
      }
      elsif ($self->{EXPERIMENTS}{$exp_name}->isa('Bench::Test::Local')) {
          $self->{EXPERIMENTS}{$exp_name}->execute_preamble;
      }
   }

   my $view = Bench::Test::View->new;

   $view->add_row ( $entry->get_field_names );

   # Tomamos el array TEST como array para mantener el orden
   # Por ello, no recorremos con for (keys @array)
   # Usamos entonces while (@array) { $key, $value = splice ... }
   while (@{$self->{TESTS}}) {
      my ($test, $params) = splice @{$self->{TESTS}}, 0, 2;

      my $entry = Bench::Test::Entry->new(
                                          fields       => $self->{FIELDS},
                                          exp_names    => $self->{SELECTED},
                                          unique       => $self->{UNIQUE},
                                  #        outputformat => $self->{OUTPUTFORMAT}
                                         ); 
      

      for my $exp_name (@{$self->{SELECTED}}) {
         my $output = ($self->{EXPERIMENTS}{$exp_name}->get_result($params, $test) or '');
         $self->fill_entry_with_fields($entry, $exp_name, $output);
      }

      $entry->set_test_field;
      $entry->set_name_field($test);

      $view->add_row ( $entry->get_field_values );
   }

   while (@{$self->{OUTPUTFORMAT}}) {
      my ($format, $file) = splice @{$self->{OUTPUTFORMAT}}, 0, 2;

      my $output = $view->process($format);

      if ($file) {
         open OUT, "> $file";
         print OUT $output;
         close OUT;
      }
      else {
         print $output;
      }
   }

   for my $exp_name (@{$self->{SELECTED}}) {
      unless ($self->{EXPERIMENTS}{$exp_name}->isa('Bench::Test::Base')) {
          $self->{EXPERIMENTS}{$exp_name}->execute_postamble;
      }
   }

   return 1;
}


#=============================================================================
# Create tests
#  - Inputs: None (See config file)
#  - Output: Test results
#============================================================================
sub create_tests {
   my $self = shift;
   
   for my $exp_name (@{$self->{SELECTED}}) {
      if (not $self->{EXPERIMENTS}{$exp_name}->isa('Bench::Test::Base')) {
          $self->{EXPERIMENTS}{$exp_name}->execute_preamble;
      }
   }

   # Tomamos el array TEST como array para mantener el orden
   # Por ello, no recorremos con for (keys @array)
   # Usamos entonces while (@array) { $key, $value = splice ... }
   #
   while (@{$self->{TESTS}}) {
      my ($test, $params) = splice @{$self->{TESTS}}, 0, 2;

      for my $exp_name (@{$self->{SELECTED}}) {
         if (not $self->{EXPERIMENTS}{$exp_name}->isa('Bench::Test::Base') and
             $self->{EXPERIMENTS}{$exp_name}{BASEDIR}) {
            $self->{EXPERIMENTS}{$exp_name}->save_result($params, $test);
         }
      }
   }

   for my $exp_name (@{$self->{SELECTED}}) {
      if ($self->{EXPERIMENTS}{$exp_name}->isa('Bench::Test::Remote')) {
          $self->{EXPERIMENTS}{$exp_name}->execute_postamble;
      }
   }
}

1;

__END__
